package org.example.rpc;

import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.List;

import static org.example.rpc.RpcFactory.*;

/**
 * @ClassName ProxyService
 * @Description: TODO
 * @Author mxzhang.ethan
 * @Date 2022/4/13
 * @Version V1.0
 **/
@Slf4j
public class ProxyService implements InvocationHandler {
    private static ProxyService proxyService = new ProxyService();

    @SuppressWarnings("unchecked")
    public static <T> T proxy(Class<T> serviceClasz) {
        return (T) Proxy.newProxyInstance(serviceClasz.getClassLoader(), new Class<?>[]{serviceClasz}, proxyService);
    }

    /**
     * client端将所有的方法都包装为rpcRequest，通过zookeeper找到server端的地址，将rpcRequest发给server端，然后获取方法执行结果
     *
     * @param proxy  代理对象
     * @param method 方法
     * @param args   参数
     * @return 方法执行结果
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Class<?> serviceClasz = method.getDeclaringClass();
        String typeName = serviceClasz.getTypeName();
        CuratorFramework zkClient = RpcFactory.zkClient();
        List<String> addrList = zkClient.getChildren().forPath(ZOOKEEPER_PATH_PREFIX + typeName);
        if (addrList == null || addrList.isEmpty()) {
            log.error("ZkAddr{} can not find service:[{}]", ZK_ADDR, typeName);
            return null;
        }
        String[] serverAddrDetail = addrList.get(0).split(":");
        InetSocketAddress serviceAddr = new InetSocketAddress(serverAddrDetail[0], Integer.parseInt(serverAddrDetail[1]));
        //发送rpcRequest请求并且接收rpcResponse，rpcResponse中有方法的执行结果
        Object responseObj = requestAndRecevive(serviceAddr,
                RpcRequest.builder().parameterType(method.getParameterTypes()).typeName(typeName).args(args).methodName(method.getName()).build());
        if (responseObj == null) {
            log.error("[{}]未接收到rpc response", serviceAddr);
            return null;
        }
        if (!(responseObj instanceof RpcResponse)) {
            log.error("[{}]非法回复", serviceAddr);
            return null;
        }
        RpcResponse response = (RpcResponse) responseObj;
        if (RpcResponse.FAIL == response.getStatus()) {
            log.error("[{}]请求执行过程中失败", serviceAddr);
            return null;
        }
        if (RpcResponse.FIND != response.getStatus()) {
            log.error("[{}]未知状态", serviceAddr);
            return null;
        }
        return response.getResponseObj();
    }

    private Object requestAndRecevive(InetSocketAddress socketAddress, RpcRequest request) {
        Object res = null;
        try (Socket socket = new Socket(socketAddress.getHostString(), socketAddress.getPort(), InetAddress.getLocalHost(), ClIENT_PORT);
             ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
             ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream())) {
            objectOutputStream.writeObject(request);
            objectOutputStream.flush();
            res = objectInputStream.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return res;
    }
}
